package com.hrsaas.ext.spider.core;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;

import com.hrsaas.ext.spider.err.Exceptions;

import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.EncoderException;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.CharsetUtil;

/**
 * <p>
 * 功能：响应结果处理， ResponseHandler#receive 方法来接收响应结果
 * <p>
 * 
 * @author wangxiaoliang@kanzhun.com
 *
 *         2018年7月31日
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public abstract class ResponseHandler<T> {

	private final Class<T> type;
	private final CountDownLatch latch = new CountDownLatch(1);
	private HttpClient httpClient;
	private CookieStore cookieStore = null;
	private String domain;
	private String redirectUrl;

	public ResponseHandler() {
		this.type = getClassType();
	}

	void setCookieStore(CookieStore cookieStore) {
		this.cookieStore = cookieStore;
	}

	protected Class<T> getClassType() {
		try {
			Type types = this.getClass().getGenericSuperclass();
			Type classType = ((ParameterizedType) types).getActualTypeArguments()[0];
			return (Class) classType;
		} catch (Exception e) {
			return (Class<T>) String.class;
		}
	}

	public void await() throws InterruptedException {
		latch.await();
	}

	public boolean await(long l, TimeUnit tu) throws InterruptedException {
		return latch.await(l, tu);
	}

	void setClient(HttpClient httpClient) {
		this.httpClient = httpClient;
	}

	public void setDomain(String host) {
		this.domain = host;
	}

	/**
	 * 请求url传递到response handler中
	 * 
	 * @param url
	 */
	public void setRedirectUrl(String redirectUrl) {
		this.redirectUrl = redirectUrl;
	}

	/**
	 * 跳转url
	 * 
	 * @return
	 */
	public String getRedirectUrl() {
		return this.redirectUrl;
	}

	final String compress_header = "Content-Encoding";

	protected void internalReceive(HttpResponseStatus status, HttpHeaders headers, ByteBuf content) {
		byte[] uncompress = getBytes(content);
		if (headers.contains(compress_header)) {
			uncompress = uncompress(uncompress);
		}
		try {
			if (status.code() > 399) {
				onErrorResponse(status, headers, getConent(uncompress));
				return;
			}
			_receive(status, headers, uncompress);
		} catch (Exception ex) {
			content.resetReaderIndex();
			onErrorResponse(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE, headers, getConent(uncompress));
			Exceptions.chuck(ex);
		} finally {
			latch.countDown();
		}
	}

	/**
	 * 存储header及cookie的信息
	 * 
	 * @param status
	 * @param headers
	 * @param convert
	 */
	protected void _receive(HttpResponseStatus status, HttpHeaders headers, byte[] content) {
		if (headers != null && !headers.isEmpty()) {
			filterCookies(headers);
		}
		try {
			T object = (T) convert(status, headers, content);
			if (receive(status, cookieStore, headers, object)) {
				next(httpClient, object, cookieStore, headers);
			}
		} catch (IllegalArgumentException e) {
			onErrorResponse(status, headers,
			        "response convert json exception : type " + getType() + " message: " + e.getMessage());
		} catch (Exception e) {
			onErrorResponse(status, headers, getConent(content));
		}
	}

	/**
	 * 过滤cookies
	 * 
	 * @param headers
	 * @return
	 */
	protected CookieStore filterCookies(HttpHeaders headers) {
		CookieStore cookieStore = this.cookieStore;
		if (cookieStore == null)
			cookieStore = this.cookieStore = new CookieStore();
		cookieStore.extractasync(headers, domain);
		headers.remove(HttpHeaderNames.SET_COOKIE);
		return cookieStore;
	}

	public byte[] uncompress(byte[] bytes) {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		ByteArrayInputStream in = new ByteArrayInputStream(bytes);
		try {
			GZIPInputStream ungzip = new GZIPInputStream(in);
			byte[] buffer = new byte[1024];
			int n;
			while ((n = ungzip.read(buffer)) >= 0) {
				out.write(buffer, 0, n);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return out.toByteArray();
	}

	/**
	 * 获取类型
	 * 
	 * @return
	 */
	protected Class<T> getType() {
		return type;
	}

	/**
	 * 默认 UTF-8 编码
	 * 
	 * @param content
	 * @return
	 */
	protected String getConent(byte[] content) {
		return getConent(content, CharsetUtil.UTF_8.name());
	}

	/**
	 * 转换成string
	 * 
	 * @param content
	 * @return
	 */
	protected String getConent(byte[] content, String charset) {
		try {
			return new String(content, charset);
		} catch (UnsupportedEncodingException e) {
			throw new EncoderException("can not encoding content from response");
		}
	}

	protected byte[] getBytes(ByteBuf buffer) {
		byte[] dst = new byte[buffer.readableBytes()];
		buffer.readBytes(dst);
		return dst;
	}

	/**
	 * 转换成期望有类型
	 * 
	 * @param headers
	 * @param status
	 * @param content
	 * @return
	 */
	protected abstract Object convert(HttpResponseStatus status, HttpHeaders headers, byte[] content);

	/**
	 * 接收响应处理
	 * 
	 * @param response
	 *            status
	 * @param response
	 *            headers
	 * @param object
	 * @return 是否进行下一步next处理，true是，flase否
	 */
	protected abstract boolean receive(HttpResponseStatus status, CookieStore cookieStore, HttpHeaders headers,
	        T object);

	/**
	 * 消息接收之后发起下一步处理
	 * 
	 * @param client
	 *            全局httpclient
	 * @param object
	 *            响应实体
	 * @param cookieStore
	 *            响应cookie
	 * @param headers
	 *            预备请求 headers，已经过滤Date,connect,host等header
	 */
	protected abstract void next(HttpClient client, T object, CookieStore cookieStore, HttpHeaders headers);

	/**
	 * 错误消息处理
	 * 
	 * @param status
	 *            响应状态码
	 * @param headers
	 *            响应头
	 * @param content
	 *            错误内容
	 */
	protected abstract void onErrorResponse(HttpResponseStatus status, HttpHeaders headers, String content);

	void onError(Throwable err) {
		err.printStackTrace();
	}

}
